﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;
using System.Windows.Data;
using System.Collections.ObjectModel;

namespace SamSoft.Controls
{
    public class ListBoxCheckBox : ListBox
    {
        /// <summary>
        /// Constructeur
        /// </summary>

        public ListBoxCheckBox()
        {
            this.CheckedItemsSource = new ObservableCollection<object>();
        }

        /// <summary>
        /// ne plus effectuer le lien entre items et CheckedItems permet d'accélérer le traitement de retrait en masse d'items
        /// </summary>

        public void UnlinkItemsToCheckedItems()
        {
            this.IsItemsToCheckedItemsUnlinked = true;
        }

        /// <summary>
        /// Remettre le lien
        /// </summary>

        public void LinkItemsToCheckedItems()
        {
            this.IsItemsToCheckedItemsUnlinked = false;
        }

        /// <summary>
        /// Lien entre Items et CheckedItems
        /// </summary>

        private bool IsItemsToCheckedItemsUnlinked
        {
            get;
            set;
        }

        /// <summary>
        /// Ajout d'un item
        /// </summary>
        /// <param name="e"></param>

        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // pas de lien items/CheckedItems pour accélérer les traitemments de retraits
            if (this.IsItemsToCheckedItemsUnlinked == true)
            {
                return;
            }

            switch(e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove :
                    
                    foreach( object data in e.OldItems)
                    {
                        if (this.CheckedItemsSource.Contains(data) == true)
                        {
                            this.CheckedItemsSource.Remove(data);
                        }
                    }
                    break;

                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:

                    this.CheckedItemsSource.Clear();
                    break;
            }

            base.OnItemsChanged(e);
        }

        /// <summary>
        /// Affichage des CheckBoxs
        /// </summary>

        public bool ShowCheckBox
        {
            get { return (bool)GetValue(ShowCheckBoxProperty); }
            set { SetValue(ShowCheckBoxProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShowCheckBox.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowCheckBoxProperty =
            DependencyProperty.Register("ShowCheckBox", typeof(bool), typeof(ListBoxCheckBox), new PropertyMetadata(false));

        /// <summary>
        /// Style de la CheckBox par defaut
        /// </summary>

        public Style CheckBoxStyle
        {
            get { return (Style)GetValue(CheckBoxStyleProperty); }
            set { SetValue(CheckBoxStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CheckBoxStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CheckBoxStyleProperty =
            DependencyProperty.Register("CheckBoxStyle", typeof(Style), typeof(ListBoxCheckBox), new PropertyMetadata(null, OnCheckBoxStyleChanged));

        /// <summary>
        /// Changemment de style
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private static void OnCheckBoxStyleChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ListBoxCheckBox listbox = sender as ListBoxCheckBox;

            // fixer les items existants
            // les autres sont traité lors de la création

            for (int index = 0; index < listbox.Items.Count; index++)
            {
                ListBoxCheckBoxItem item = listbox.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxCheckBoxItem;

                if (item != null)
                {
                    item.CheckBoxStyle = (Style)e.NewValue;
                }
            }
        }

        /// <summary>
        /// Obtenir le Container
        /// </summary>
        /// <returns></returns>

        protected override DependencyObject GetContainerForItemOverride()
        {
            ListBoxCheckBoxItem item = new ListBoxCheckBoxItem();

            item.SetBinding(ListBoxCheckBoxItem.ShowCheckBoxProperty,
                    new Binding()
                    {
                        Source = this,
                        Path = new PropertyPath("ShowCheckBox")
                    }
                );

            item.CheckBoxStyle = this.CheckBoxStyle;

            item.Checked += new EventHandler(item_Checked);

            return item;
        }

        /// <summary>
        /// Tout est coché
        /// </summary>

        public void CheckAll()
        {
            ObservableCollection<object> items = this.CheckedItemsSource;
            this.CheckedItemsSource = null;

            foreach (object data in this.ItemsSource)
            {
                items.Add(data);
            }

            this.CheckedItemsSource = items;

            for (int index = 0; index < this.Items.Count; index++)
            {
                ListBoxCheckBoxItem item = this.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxCheckBoxItem;

                if (item != null)
                {
                    item.IsChecked = true;
                }
            }
        }

        /// <summary>
        /// Rien n'est coché
        /// </summary>

        public void UncheckAll()
        {
            this.CheckedItemsSource.Clear();
        }

        /// <summary>
        /// Inverser
        /// </summary>

        public void InvertAllChecked()
        {
            //foreach (object data in this.ItemsSource)
            //{
            //    if (this.CheckedItemsSource.Contains(data) == true)
            //    {
            //        this.CheckedItemsSource.Remove(data);
            //    }
            //    else
            //    {
            //        this.CheckedItemsSource.Add(data);
            //    }
            //}

            // Tout est selectionné
            if (this.CheckedItemsSource.Count == this.Items.Count)
            {
                this.UncheckAll();
                return;
            }

            ObservableCollection<object> checkedItems = new ObservableCollection<object>();
            ObservableCollection<object> items = new ObservableCollection<object>();

            foreach (object data in this.CheckedItemsSource)
            {
                checkedItems.Add(data);
            }

            this.CheckedItemsSource = null;
                
            foreach (object data in this.Items)
            {
                if (checkedItems.Contains(data) == false)
                {
                    items.Add(data);
                }
            }

            this.CheckedItemsSource = items;

            // fixer les items existants

            for (int index = 0; index < this.Items.Count; index++)
            {
                ListBoxCheckBoxItem item = this.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxCheckBoxItem;

                if (item != null)
                {
                    item.IsChecked = !checkedItems.Contains(item.DataContext);
                }
            }
        }

        /// <summary>
        /// Retirer les items selectionnés
        /// </summary>
        /// <param name="removeAction"></param>

        public void RemoveCheckedItems( Action<object> removeAction  )
        {
            this.UnlinkItemsToCheckedItems();

            try
            {
                foreach (object data in this.CheckedItemsSource)
                {
                    removeAction(data);
                }
            }
            finally
            {
                this.LinkItemsToCheckedItems();
            }

            this.CheckedItemsSource.Clear();
        }

        /// <summary>
        /// Retirer les elements checké si Items source est une observableCollection
        /// </summary>
        /// <typeparam name="T"></typeparam>

        public void RemoveCheckedItemsFromObservableCollection<T>()
        {
            ObservableCollection<T> items = this.ItemsSource as ObservableCollection<T>;

    this.RemoveCheckedItems(
        (data) =>
        {
            items.Remove((T)data);
        }
    );
        }

        /// <summary>
        /// La donnée est appliqué au ListBoxCheckBoxItem
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            if (this.CheckedItemsSource != null)
            {
                ListBoxCheckBoxItem listBoxCheckBoxItem = element as ListBoxCheckBoxItem;

                listBoxCheckBoxItem.IsChecked = this.CheckedItemsSource.Contains(item);
            }

            base.PrepareContainerForItemOverride(element, item);
        }

        /// <summary>
        /// Le check de l'item vient de changer !
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        void item_Checked(object sender, EventArgs e)
        {
            ListBoxCheckBoxItem item = sender as ListBoxCheckBoxItem;
            object value = item.DataContext;

            if (this.CheckedItemsSource == null)
            {
                this.CheckedItemsSource = new ObservableCollection<object>();
            }

            if (item.IsChecked == true)
            {
                // on ajoute la valeur si elle n'existe pas dans la liste
                if (this.CheckedItemsSource.Contains(value) == false)
                {
                    this.CheckedItemsSource.Add(value);
                }
            }
            else
            {
                this.CheckedItemsSource.Remove(value);
            }
        }

        /// <summary>
        /// Est-ce le bon container
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is ListBoxCheckBoxItem;
        }

        /// <summary>
        /// Les items checké
        /// </summary>

        public ObservableCollection<object> CheckedItemsSource
        {
            get { return (ObservableCollection<object>)GetValue(CheckedItemsSourceProperty); }
            set { SetValue(CheckedItemsSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CheckedItemsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CheckedItemsSourceProperty =
            DependencyProperty.Register("CheckedItemsSource", typeof(ObservableCollection<object>), typeof(ListBoxCheckBox), new PropertyMetadata(null, OnCheckedItemsSourceChanged));

        /// <summary>
        /// Changement
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private static void OnCheckedItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ObservableCollection<object> oldSource = e.OldValue as ObservableCollection<object>;
            ObservableCollection<object> newSource = e.NewValue as ObservableCollection<object>;

            ListBoxCheckBox listbox = sender as ListBoxCheckBox;

            listbox.CheckedItemsSourceChanged(oldSource, newSource);
        }

        /// <summary>
        /// Changement de Source
        /// </summary>
        /// <param name="oldSource"></param>
        /// <param name="newSource"></param>

        private void CheckedItemsSourceChanged(ObservableCollection<object> oldSource, ObservableCollection<object> newSource)
        {
            if (oldSource != null)
            {
                oldSource.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CheckedItemsSource_CollectionChanged);
            }

            if (newSource != null)
            {
                newSource.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CheckedItemsSource_CollectionChanged);
            }
        }

        /// <summary>
        /// Place le IsChecked
        /// </summary>
        /// <param name="checkedItem"></param>

        private void PopulateIsCheckedOnItemGenerator(object checkedItem, bool isChecked)
        {
            ListBoxCheckBoxItem item = this.ItemContainerGenerator.ContainerFromItem(checkedItem) as ListBoxCheckBoxItem;

            if (item != null)
            {
                item.IsChecked = isChecked;
            }
        }

        /// <summary>
        /// Changement
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void CheckedItemsSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    foreach (object data in e.NewItems)
                    {
                        this.PopulateIsCheckedOnItemGenerator(data, true);
                    }
                    break;

                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    foreach (object data in e.OldItems)
                    {
                        this.PopulateIsCheckedOnItemGenerator(data, false);
                    }
                    break;

                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    foreach (object data in this.ItemsSource)
                    {
                        this.PopulateIsCheckedOnItemGenerator(data, false);
                    }
                    break;
            }
        }
    }
}
